﻿using System;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using System.Linq.Expressions;
using UnityEngine;

namespace LS.UnityExtensions
{
    /// <summary>
    /// 程序集相关的实用函数。
    /// </summary>
    public static partial class LSReflection
    {
        public const string MemberFieldPrefix = "m_";
        public const string ComponentTagFieldName = "tag";
        public const string ComponentNameFieldName = "name";
        public static readonly string[] ExcludedPropertyNames = new string[] { "runInEditMode", "useGUILayout", "hideFlags" };

        public static readonly Type SerializableAttributeType = typeof(System.SerializableAttribute);
        public static readonly Type SerializeFieldAttributeType = typeof(SerializeField);
        public static readonly Type ObsoleteAttributeType = typeof(System.ObsoleteAttribute);
        public static readonly Type NonSerializedAttributeType = typeof(System.NonSerializedAttribute);
        public static readonly Type PropertyNotSerializableAttributeType = typeof(PropertyNotSerializableAttribute);
        public static readonly Type PropertySerializableAttribute = typeof(PropertySerializableAttribute);
        
        private static readonly List<System.Reflection.Assembly> s_Assemblies = new List<Assembly>();
        private static readonly Dictionary<string, Type> s_CachedTypes = new Dictionary<string, Type>(StringComparer.Ordinal);
        
        public static IReadOnlyList<System.Reflection.Assembly> Assemblies => s_Assemblies;
        public static IReadOnlyDictionary<string, Type>  CachedTypes => s_CachedTypes;

        static LSReflection()
        {
            RefreshAssemblies();
        }

        public static void RefreshAssemblies()
        {
            s_Assemblies.Clear();
            s_Assemblies.AddRange(AppDomain.CurrentDomain.GetAssemblies());
        }
        /// <summary>
        /// 获取已加载的程序集。
        /// </summary>
        /// <returns>已加载的程序集。</returns>
        public static System.Reflection.Assembly[] GetAssemblies()
        {
            return s_Assemblies.ToArray();
        }

        /// <summary>
        /// 获取已加载的程序集中的所有类型。
        /// </summary>
        /// <returns>已加载的程序集中的所有类型。</returns>
        public static Type[] GetTypes()
        {
            List<Type> results = new List<Type>();
            foreach (System.Reflection.Assembly assembly in s_Assemblies)
            {
                results.AddRange(assembly.GetTypes());
            }

            return results.ToArray();
        }

        /// <summary>
        /// 获取已加载的程序集中的所有类型。
        /// </summary>
        /// <param name="results">已加载的程序集中的所有类型。</param>
        public static void GetTypes(List<Type> results)
        {
            if (results == null)
            {
                throw new Exception("Results is invalid.");
            }

            results.Clear();
            foreach (System.Reflection.Assembly assembly in s_Assemblies)
            {
                results.AddRange(assembly.GetTypes());
            }
        }

        public static Type[] GetExtendTypes(Type baseType, bool filterCreatable = true)
        {
            var ienum = GetTypes()
                .Where(t => IsAssignableFrom(baseType, t));
            
            if (filterCreatable)
            {
                ienum = ienum.Where(t=>!t.IsAbstract && !t.IsInterface);
            }

            return ienum.ToArray();
        }
        
        public static Type[] GetExtendClasses(Type baseType)
        {
            return GetTypes()
                .Where(t => t.IsClass && !t.IsAbstract && IsAssignableFrom(baseType, t))
                .ToArray();
        }

        /*	
		 * 	Gets the element type of a collection or array.
		 * 	Returns null if type is not a collection type.
		 */
        public static Type[] GetElementTypes(Type type)
        {
            if (IsGenericType(type))
                return LSReflection.GetGenericArguments(type);
            else if (type.IsArray)
                return new Type[] { LSReflection.GetElementType(type) };
            else
                return Array.Empty<Type>();
        }
    
        public static List<FieldInfo> GetSerializableFields(Type type, List<FieldInfo> serializableFields = null, string[] memberNames = null, BindingFlags bindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)
        {
            if (type == null)
                return new List<FieldInfo>();

            var fields = type.GetFields(bindings);

            if (serializableFields == null)
                serializableFields = new List<FieldInfo>();

            foreach (var field in fields)
            {
                var fieldName = field.Name;

                // If a members array was provided as a parameter, only include the field if it's in the array.
                if (memberNames != null)
                    if (!memberNames.Contains(fieldName))
                        continue;

                var fieldType = field.FieldType;

                if (!field.IsPublic)
                {
                    // If the field is private/protected/internal, only serialize it if it's explicitly marked as serializable.
                    if (!AttributeIsDefined(field, SerializeFieldAttributeType) && !AttributeIsDefined(field, SerializableAttributeType))
                        continue;
                }

                // Exclude const or readonly fields.
                if (field.IsLiteral || field.IsInitOnly)
                    continue;

                // Don't store fields whose type is the same as the class the field is housed in unless it's stored by reference (to prevent cyclic references)
                if (fieldType == type && !IsAssignableFrom(typeof(UnityEngine.Object), fieldType))
                    continue;

                // If property is marked as obsolete or non-serialized, don't serialize it.
                if (AttributeIsDefined(field, NonSerializedAttributeType) || AttributeIsDefined(field, ObsoleteAttributeType))
                    continue;

                if (!TypeIsSerializable(field.FieldType))
                    continue;

                // Don't serialize member fields.
                if (fieldName.StartsWith(MemberFieldPrefix) && field.DeclaringType.Namespace != null && field.DeclaringType.Namespace.Contains("UnityEngine"))
                    continue;

                serializableFields.Add(field);
            }

            var baseType = BaseType(type);
            if (baseType != null && baseType != typeof(System.Object) && baseType != typeof(UnityEngine.Object))
                GetSerializableFields(BaseType(type), serializableFields, memberNames);

            return serializableFields;
        }

        public static List<PropertyInfo> GetSerializableProperties(Type type, List<PropertyInfo> serializableProperties = null, string[] memberNames = null, BindingFlags bindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)
        {
            bool isComponent = IsAssignableFrom(typeof(UnityEngine.Component), type);

            var properties = type.GetProperties(bindings);

            if (serializableProperties == null)
                serializableProperties = new List<PropertyInfo>();

            foreach (var p in properties)
            {
                var propertyName = p.Name;

                if (ExcludedPropertyNames.Contains(propertyName))
                    continue;

                // If a members array was provided as a parameter, only include the property if it's in the array.
                if (memberNames != null)
                    if (!memberNames.Contains(propertyName))
                        continue;
                //至少有一个get或set访问器并且不是公有
                if ((p.GetMethod == null || !p.GetMethod.IsPublic) && (p.SetMethod == null || !p.SetMethod.IsPublic) && (p.GetMethod != null || p.SetMethod != null))
                {
                    // If property is private/protected/internal, only get properties which are explicitly marked as serializable.
                    if (!AttributeIsDefined(p, SerializeFieldAttributeType) && !AttributeIsDefined(p, PropertySerializableAttribute) && !AttributeIsDefined(p, SerializableAttributeType))
                        continue;
                }

                var propertyType = p.PropertyType;

                // Don't store properties whose type is the same as the class the property is housed in unless it's stored by reference (to prevent cyclic references)
                if (propertyType == type && !IsAssignableFrom(typeof(UnityEngine.Object), propertyType))
                    continue;

                if (!p.CanRead || !p.CanWrite)
                    continue;

                // Only support properties with indexing if they're an array.
                if (p.GetIndexParameters().Length != 0 && !propertyType.IsArray)
                    continue;

                // Check that the type of the property is one which we can serialize.
                // Also check whether an ES3Type exists for it.
                if (!TypeIsSerializable(propertyType))
                    continue;

                // Ignore certain properties on components.
                if (isComponent)
                {
                    // Ignore properties which are accessors for GameObject fields.
                    if (propertyName == ComponentTagFieldName || propertyName == ComponentNameFieldName)
                        continue;
                }

                // If property is marked as obsolete or non-serialized, don't serialize it.
                if (AttributeIsDefined(p, ObsoleteAttributeType) || AttributeIsDefined(p, NonSerializedAttributeType) || AttributeIsDefined(p, PropertyNotSerializableAttributeType))
                    continue;

                serializableProperties.Add(p);
            }

            var baseType = BaseType(type);
            if (baseType != null && baseType != typeof(System.Object))
                GetSerializableProperties(baseType, serializableProperties, memberNames);

            return serializableProperties;
        }

        public static bool TypeIsSerializable(Type type)
        {
            if (type == null)
                return false;
        
            if (IsPrimitive(type) || IsValueType(type) || IsAssignableFrom(typeof(UnityEngine.Component), type) || IsAssignableFrom(typeof(UnityEngine.ScriptableObject), type))
                return true;
        
            // var es3Type = ES3TypeMgr.GetOrCreateES3Type(type, false);
            //
            // if (es3Type != null && !es3Type.isUnsupported)
            //     return true;
        
            if (TypeIsArray(type))
            {
                if (TypeIsSerializable(type.GetElementType()))
                    return true;
                return false;
            }
        
            var genericArgs = type.GetGenericArguments();
            for (int i = 0; i < genericArgs.Length; i++)
                if (!TypeIsSerializable(genericArgs[i]))
                    return false;
        
            /*if (HasParameterlessConstructor(type))
                return true;*/
            return false;
        }

        public static System.Object CreateInstance(Type type)
        {
            if (IsAssignableFrom(typeof(UnityEngine.Component), type))
                return CreateComponent(type);
            else if (IsAssignableFrom(typeof(ScriptableObject), type))
                return ScriptableObject.CreateInstance(type);
            else if (LSReflection.HasParameterlessConstructor(type))
                return Activator.CreateInstance(type);
            else
            {
#if NETFX_CORE
                throw new NotSupportedException($"Cannot create an instance of {type} because it does not have a parameterless constructor, which is required on Universal Windows platform.");
#else
                return System.Runtime.Serialization.FormatterServices.GetUninitializedObject(type);
#endif
            }
        }

        public static System.Object CreateInstance(Type type, params object[] args)
        {
            if (IsAssignableFrom(typeof(UnityEngine.Component), type))
                return CreateComponent(type);
            else if (IsAssignableFrom(typeof(ScriptableObject), type))
                return ScriptableObject.CreateInstance(type);
            return Activator.CreateInstance(type, args);
        }

        public static Array ArrayCreateInstance(Type type, int length)
        {
            return Array.CreateInstance(type, new int[] { length });
        }

        public static Array ArrayCreateInstance(Type type, int[] dimensions)
        {
            return Array.CreateInstance(type, dimensions);
        }

        public static Array ArrayEmpty(Type type)
        {
            return Array.CreateInstance(type,  0);
        }
        
        public static Component CreateComponent(Type type, GameObject gameObject = null)
        {
            if(gameObject == null)
                gameObject = new GameObject("Reflection Component Object");
            
            if (type == typeof(Transform))
                return gameObject.GetComponent(type);

            return gameObject.AddComponent(type);
        }
        
        public static Type MakeGenericType(Type type, Type genericParam)
        {
            return type.MakeGenericType(genericParam);
        }

        public static ReflectedMember[] GetSerializableMembers(Type type, string[] memberNames = null)
        {
            if (type == null)
                return new ReflectedMember[0];

            var fieldInfos = GetSerializableFields(type, new List<FieldInfo>(), memberNames);
            var propertyInfos = GetSerializableProperties(type, new List<PropertyInfo>(), memberNames);
            var reflectedFields = new ReflectedMember[fieldInfos.Count + propertyInfos.Count];

            for (int i = 0; i < fieldInfos.Count; i++)
                reflectedFields[i] = new ReflectedMember(fieldInfos[i]);
            for (int i = 0; i < propertyInfos.Count; i++)
                reflectedFields[i + fieldInfos.Count] = new ReflectedMember(propertyInfos[i]);

            return reflectedFields;
        }

        public static ReflectedMember GetReflectedProperty(Type type, string propertyName)
        {
            var propertyInfo = LSReflection.GetProperty(type, propertyName);
            return new ReflectedMember(propertyInfo);
        }

        public static ReflectedMember GetReflectedMember(Type type, string fieldName)
        {
            var fieldInfo = LSReflection.GetField(type, fieldName);
            return new ReflectedMember(fieldInfo);
        }

        /*
		 * 	Finds all classes of a specific type, and then returns an instance of each.
		 * 	Ignores classes which can't be instantiated (i.e. abstract classes, those without parameterless constructors).
		 */
        public static IList<T> GetInstances<T>()
        {
            var instances = new List<T>();
            foreach (var assembly in Assemblies)
                foreach (var type in assembly.GetTypes())
                    if (IsAssignableFrom(typeof(T), type) && LSReflection.HasParameterlessConstructor(type) && !LSReflection.IsAbstract(type))
                        instances.Add((T)Activator.CreateInstance(type));
            return instances;
        }

        public static IList<Type> GetDerivedTypes(Type derivedType)
        {
            return
                (
                    from assembly in Assemblies
                    from type in assembly.GetTypes()
                    where IsAssignableFrom(derivedType, type)
                    select type
                ).ToList();
        }

        public static bool IsAssignableFrom(Type a, Type b)
        {
            return a.IsAssignableFrom(b);
        }

        public static Type GetGenericTypeDefinition(Type type)
        {
            return type.GetGenericTypeDefinition();
        }

        public static Type[] GetGenericArguments(Type type)
        {
            return type.GetGenericArguments();
        }

        public static int GetArrayRank(Type type)
        {
            return type.GetArrayRank();
        }

        public static string GetAssemblyQualifiedName(Type type)
        {
            return type.AssemblyQualifiedName;
        }

        public static ReflectedMethod GetMethod(Type type, string methodName, Type[] genericParameters, Type[] parameterTypes)
        {
            return new ReflectedMethod(type, methodName, genericParameters, parameterTypes);
        }

        public static bool TypeIsArray(Type type)
        {
            return type.IsArray;
        }

        public static Type GetElementType(Type type)
        {
            return type.GetElementType();
        }

#if NETFX_CORE
		public static bool IsAbstract(Type type)
		{
		return type.GetTypeInfo().IsAbstract;
		}

		public static bool IsInterface(Type type)
		{
		return type.GetTypeInfo().IsInterface;
		}

		public static bool IsGenericType(Type type)
		{
		return type.GetTypeInfo().IsGenericType;
		}

		public static bool IsValueType(Type type)
		{
		return type.GetTypeInfo().IsValueType;
		}

		public static bool IsEnum(Type type)
		{
		return type.GetTypeInfo().IsEnum;
		}

		public static bool HasParameterlessConstructor(Type type)
		{
		    return GetParameterlessConstructor(type) != null;
		}

		public static ConstructorInfo GetParameterlessConstructor(Type type)
		{
		    foreach (var cInfo in type.GetTypeInfo().DeclaredConstructors)
		        if (!cInfo.IsStatic && cInfo.GetParameters().Length == 0)
		            return cInfo;
		    return null;
		}

		public static string GetShortAssemblyQualifiedName(Type type)
		{
		if (IsPrimitive (type))
		return type.ToString ();
		return type.FullName + "," + type.GetTypeInfo().Assembly.GetName().Name;
		}

		public static PropertyInfo GetProperty(Type type, string propertyName)
		{
        	var property = type.GetTypeInfo().GetDeclaredProperty(propertyName);
            if (property == null && type.BaseType != typeof(object))
                return GetProperty(type.BaseType, propertyName);
            return property;
		}

		public static FieldInfo GetField(Type type, string fieldName)
		{
		return type.GetTypeInfo().GetDeclaredField(fieldName);
		}

        public static MethodInfo[] GetMethods(Type type, string methodName)
        {
            return type.GetTypeInfo().GetDeclaredMethods(methodName);
        }

		public static bool IsPrimitive(Type type)
		{
		return (type.GetTypeInfo().IsPrimitive || type == typeof(string) || type == typeof(decimal));
		}

		public static bool AttributeIsDefined(MemberInfo info, Type attributeType)
		{
		var attributes = info.GetCustomAttributes(attributeType, true);
		foreach(var attribute in attributes)
		return true;
		return false;
		}

		public static bool AttributeIsDefined(Type type, Type attributeType)
		{
		var attributes = type.GetTypeInfo().GetCustomAttributes(attributeType, true);
		foreach(var attribute in attributes)
		return true;
		return false;
		}

		public static bool ImplementsInterface(Type type, Type interfaceType)
		{
		return type.GetTypeInfo().ImplementedInterfaces.Contains(interfaceType);
		}

        public static Type BaseType(Type type)
        {
            return type.GetTypeInfo().BaseType;
        }
#else
        public static bool IsAbstract(Type type)
        {
            return type.IsAbstract;
        }

        public static bool IsInterface(Type type)
        {
            return type.IsInterface;
        }

        public static bool IsGenericType(Type type)
        {
            return type.IsGenericType;
        }

        public static bool IsValueType(Type type)
        {
            return type.IsValueType;
        }

        public static bool IsEnum(Type type)
        {
            return type.IsEnum;
        }

        public static bool HasParameterlessConstructor(Type type)
        {
            if (IsValueType(type) || GetParameterlessConstructor(type) != null)
                return true;
            return false;
        }

        public static ConstructorInfo GetParameterlessConstructor(Type type)
        {
            var constructors = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

            foreach (var constructor in constructors)
                if (constructor.GetParameters().Length == 0)
                    return constructor;
            return null;
        }

        public static string GetShortAssemblyQualifiedName(Type type)
        {
            if (IsPrimitive(type))
                return type.ToString();
            return type.FullName + "," + type.Assembly.GetName().Name;
        }

        public static PropertyInfo GetProperty(Type type, string propertyName)
        {
            var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            if (property == null && BaseType(type) != typeof(object))
                return GetProperty(BaseType(type), propertyName);
            return property;
        }

        public static FieldInfo GetField(Type type, string fieldName)
        {
            var field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            if (field == null && BaseType(type) != typeof(object))
                return GetField(BaseType(type), fieldName);
            return field;
        }

        public static MethodInfo[] GetMethods(Type type, string methodName)
        {
            return type.GetMethods().Where(t => t.Name == methodName).ToArray();
        }

        public static bool IsPrimitive(Type type)
        {
            return (type.IsPrimitive || type == typeof(string));
        }

        public static bool AttributeIsDefined(MemberInfo info, Type attributeType)
        {
            return Attribute.IsDefined(info, attributeType, true);
        }

        public static bool AttributeIsDefined(Type type, Type attributeType)
        {
            return type.IsDefined(attributeType, true);
        }

        public static bool ImplementsInterface(Type type, Type interfaceType)
        {
            return (type.GetInterface(interfaceType.Name) != null);
        }

        
        public static Type BaseType(Type type)
        {
            return type.BaseType;
        }

        /// <summary>
        /// 获取已加载的程序集中的指定类型。
        /// </summary>
        /// <param name="typeName">要获取的类型名。</param>
        /// <returns>已加载的程序集中的指定类型。</returns>
        public static Type GetType(string typeName)
        {
            if (string.IsNullOrEmpty(typeName))
            {
                throw new Exception("Type name is invalid.");
            }

            Type type = null;
            string singleName = typeName;
            
            if (s_CachedTypes.TryGetValue(typeName, out type))
            {
                return type;
            }

            if (s_PrimitiveFullToSingleNameMap.TryGetValue(typeName, out singleName))
            {
                    
            }
            else if(s_UnityFullToSingleNameMap.TryGetValue(typeName, out singleName))
            {
                
            }
            else if (s_PrimitiveAssemblyQualifiedToSingleNameMap.TryGetValue(typeName, out singleName))
            {
                
            }
            else if (s_UnityAssemblyQualifiedToSingleNameMap.TryGetValue(typeName, out singleName))
            {
                
            }
            
            if (s_PrimitiveTypeSingleNameMap.TryGetValue(singleName, out type))
            {
                
            }
            else if(s_UnityTypeSingleNameMap.TryGetValue(singleName, out type))
            {
                
            }
            else
            {
                switch (singleName)
                {
                    case "MeshFilter":
                        type =  typeof(MeshFilter);
                        break;
                    case "Transform":
                        type =  typeof(Transform);
                        break;
                    case "Component":
                        type =  typeof(UnityEngine.Component);
                        break;
                    case "Material":
                        type =  typeof(Material);
                        break;
                    case "Texture2D":
                        type =  typeof(Texture2D);
                        break;
                    case "GameObject":
                        type =  typeof(GameObject);
                        break;
                    case "UnityEngine.Object":
                        type =  typeof(UnityEngine.Object);
                        break;
                }
            }
            
            if (type != null)
            {
                s_CachedTypes.Add(typeName, type);
                return type;
            }
            
            type = Type.GetType(typeName);
            if (type != null)
            {
                s_CachedTypes.Add(typeName, type);
                return type;
            }

            foreach (System.Reflection.Assembly assembly in s_Assemblies)
            {
                type = assembly.GetType(typeName,false);
                if (type != null)
                {
                    s_CachedTypes.Add(typeName, type);
                    return type;
                }
            }
            return null;
        }
        
        public static string GetTypeString(Type type)
        {
            string simpleName;
            if (s_PrimitiveSingleNameTypeMap.TryGetValue(type, out simpleName))
            {
                return simpleName;
            }
            else if (s_UnitySingleNameTypeMap.TryGetValue(type, out simpleName))
            {
                return simpleName;
            }
            
            if (type == typeof(Transform))
                return "Transform";
            else if (type == typeof(UnityEngine.Component))
                return "Component";
            else if (type == typeof(GameObject))
                return "GameObject";
            else if (type == typeof(MeshFilter))
                return "MeshFilter";
            else if (type == typeof(Material))
                return "Material";
            else if (type == typeof(Texture2D))
                return "Texture2D";
            else if (type == typeof(UnityEngine.Object))
                return "UnityEngine.Object";
            else
                return GetShortAssemblyQualifiedName(type);
        }
#endif

        /*
	 * 	Allows us to use FieldInfo and PropertyInfo interchangably. 
	 */
        public struct ReflectedMember
        {
            // The FieldInfo or PropertyInfo for this field.
            private FieldInfo fieldInfo;
            private PropertyInfo propertyInfo;
            public bool isProperty;

            public bool IsNull { get { return fieldInfo == null && propertyInfo == null; } }
            public string Name { get { return (isProperty ? propertyInfo.Name : fieldInfo.Name); } }
            public Type MemberType { get { return (isProperty ? propertyInfo.PropertyType : fieldInfo.FieldType); } }
            public bool IsPublic { get { return (isProperty ? (propertyInfo.GetGetMethod(true).IsPublic && propertyInfo.GetSetMethod(true).IsPublic) : fieldInfo.IsPublic); } }
            public bool IsProtected { get { return (isProperty ? (propertyInfo.GetGetMethod(true).IsFamily) : fieldInfo.IsFamily); } }
            public bool IsStatic { get { return (isProperty ? (propertyInfo.GetGetMethod(true).IsStatic) : fieldInfo.IsStatic); } }

            public ReflectedMember(System.Object fieldPropertyInfo)
            {
                if (fieldPropertyInfo == null)
                {
                    this.propertyInfo = null;
                    this.fieldInfo = null;
                    isProperty = false;
                    return;
                }

                isProperty = LSReflection.IsAssignableFrom(typeof(PropertyInfo), fieldPropertyInfo.GetType());
                if (isProperty)
                {
                    this.propertyInfo = (PropertyInfo)fieldPropertyInfo;
                    this.fieldInfo = null;
                }
                else
                {
                    this.fieldInfo = (FieldInfo)fieldPropertyInfo;
                    this.propertyInfo = null;
                }
            }

            public void SetValue(System.Object obj, System.Object value)
            {
                if (isProperty)
                    propertyInfo.SetValue(obj, value, null);
                else
                    fieldInfo.SetValue(obj, value);
            }

            public System.Object GetValue(System.Object obj)
            {
                if (isProperty)
                    return propertyInfo.GetValue(obj, null);
                else
                    return fieldInfo.GetValue(obj);
            }
        }

        public class ReflectedMethod
        {
            private MethodInfo method;

            public ReflectedMethod(Type type, string methodName, Type[] genericParameters, Type[] parameterTypes)
            {
                MethodInfo nonGenericMethod = type.GetMethod(methodName, parameterTypes);
                this.method = nonGenericMethod.MakeGenericMethod(genericParameters);
            }

            public ReflectedMethod(Type type, string methodName, Type[] genericParameters, Type[] parameterTypes, BindingFlags bindingAttr)
            {
                MethodInfo nonGenericMethod = type.GetMethod(methodName, bindingAttr, null, parameterTypes, null);
                this.method = nonGenericMethod.MakeGenericMethod(genericParameters);
            }

            public object Invoke(object obj, object[] parameters = null)
            {
                return method.Invoke(obj, parameters);
            }
        }
    }
}
